using System;
using System.Collections.Generic;
using System.Text;
using Unity.Profiling;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

public class StatsSamle
{
    public ProfilerRecorder recorder;
    public ProfilerCategory type;
    public string key;
    public string statName;
}

//https://docs.unity3d.com/Manual/ProfilerRendering.html
public class Stats : MonoBehaviour
{
    private List<StatsSamle> ls = new List<StatsSamle>();
    ProfilerRecorder mainThreadTimeRecorder;

    void StartNew(string key = "")
    {
        var obj = new StatsSamle()
        {
            key = key
        };
        ls.Add(obj);
    }

    void StartNew(ProfilerCategory type, string StatName, string key = "")
    {
        var obj = new StatsSamle();
        if (!string.IsNullOrEmpty(StatName))
        {
            obj.recorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, StatName);
            obj.type = type;
            obj.key = key == string.Empty ? StatName : key;
            obj.statName = StatName;
        }

        ls.Add(obj);
    }

    static float GetRecorderFrameAverage(ProfilerRecorder recorder)
    {
        var samplesCount = recorder.Capacity;
        if (samplesCount == 0)
            return 0;

        float r = 0;
        unsafe
        {
            var samples = stackalloc ProfilerRecorderSample[samplesCount];
            recorder.CopyTo(samples, samplesCount);
            for (var i = 0; i < samplesCount; ++i)
                r += samples[i].Value;
            r /= samplesCount;
        }

        return r;
    }

    void OnEnable()
    {
        Application.targetFrameRate = 0;
        ls.Clear();
        mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "Main Thread", 15);
        StartNew("Render");
        StartNew(ProfilerCategory.Render, "SetPass Calls Count", "SetPass Calls");
        StartNew(ProfilerCategory.Render, "Draw Calls Count", "Draw Calls");
        StartNew(ProfilerCategory.Render, "Vertices Count", "Vertices");
        StartNew(ProfilerCategory.Render, "Triangles Count", "Triangles");
        StartNew(ProfilerCategory.Render, "Shadow Casters Count", "SetPassCast");
        StartNew(ProfilerCategory.Render, "Render Textures Count");

        StartNew(" ");
        StartNew("Memory");
        StartNew(ProfilerCategory.Memory, "Total Used Memory");
        StartNew(ProfilerCategory.Memory, "Total Reserved Memory");

        StartNew(ProfilerCategory.Memory, "GC Used Memory");
        StartNew(ProfilerCategory.Memory, "GC Reserved Memory");

        StartNew(ProfilerCategory.Memory, "Gfx Used Memory");
        StartNew(ProfilerCategory.Memory, "Gfx Reserved Memory");

        StartNew(ProfilerCategory.Memory, "Audio Used Memory");
        StartNew(ProfilerCategory.Memory, "Audio Reserved Memory");

        StartNew(ProfilerCategory.Memory, "Video Used Memory");
        StartNew(ProfilerCategory.Memory, "Video Reserved Memory");

        StartNew(ProfilerCategory.Memory, "Profiler Used Memory");
        StartNew(ProfilerCategory.Memory, "Profiler Reserved Memory");
        StartNew(ProfilerCategory.Memory, "System Used Memory");
    }

    void OnDisable()
    {
        while (ls != null && ls.Count > 0)
        {
            if (!string.IsNullOrEmpty(ls[0].statName))
            {
                ls[0].recorder.Dispose();
            }

            ls.RemoveAt(0);
        }

        mainThreadTimeRecorder.Dispose();
    }

    private void OnPreRender()
    {
        lastRenderTime = Time.realtimeSinceStartup;
    }

    private void OnPostRender()
    {
        renderTime = (Time.realtimeSinceStartup - lastRenderTime) * 1000;
    }

    private float renderTime;
    private float lastRenderTime = 0f;
    public static string statsStr;

    void Update()
    {
        var sb = new StringBuilder(1000);
        var fl = GetRecorderFrameAverage(mainThreadTimeRecorder);
        fl = fl * (1e-6f);
        fps = 1000f / fl;
        AvgFps(fps, fl, renderTime);
        sb.AppendLine($"CPU {fps:F1} FPS({fl:F}ms) TFR {Application.targetFrameRate}");
        sb.AppendLine($"GPU {renderTime:F}ms");
        sb.AppendLine($"Avg CPU:{avgfps:F1} FPS({avgfpsTime:F}ms) GPU:{avgRenderTime:F}ms ");

        for (int i = 0; i < ls.Count; i++)
        {
            var obj = ls[i];
            if (string.IsNullOrEmpty(obj.statName))
            {
                sb.AppendLine($"{obj.key}");
            }
            else
            {
                if (obj.recorder.Valid)
                {
                    if (obj.type == ProfilerCategory.Memory)
                    {
                        sb.AppendLine($"{obj.key}: {obj.recorder.LastValue / 1024 / 1024} mb");
                    }
                    else
                    {
                        if (obj.recorder.LastValue > 1000)
                        {
                            sb.AppendLine($"{obj.key}: {(obj.recorder.LastValue / 1000f).ToString("0.00")} k");
                        }
                        else
                        {
                            sb.AppendLine($"{obj.key}: {obj.recorder.LastValue}");
                        }
                    }
                }
            }
        }

        statsStr = sb.ToString();
        if (txt != null)
        {
            txt.text = statsStr;
        }
    }

    public Text txt;
    // void OnGUI()
    // {
    //     GUILayout.TextArea(statsText);
    // }
    public int avgSamplingTime = 1;
    private List<float> fpsls = new List<float>();
    private List<float> fpsTimels = new List<float>();
    private List<float> renderTimels = new List<float>();
    private float fps;
    private float avgfps;
    private float avgfpsTime;
    private float avgRenderTime;
    private float lastTime;
    float total = 0;
    void AvgFps(float fps, float fpsTime, float renderTime)
    {
        if (!float.IsInfinity(fps))
            fpsls.Add(fps);
        if (!float.IsInfinity(fpsTime))
            fpsTimels.Add(fpsTime);
        if (!float.IsInfinity(renderTime))
            renderTimels.Add(renderTime);
        lastTime += Time.deltaTime;
        if (lastTime >= avgSamplingTime)
        {
            lastTime -= avgSamplingTime;
            Avg(ref fpsls, ref avgfps);
            Avg(ref fpsTimels, ref avgfpsTime);
            Avg(ref renderTimels, ref avgRenderTime);
        }
    }

    void Avg(ref List<float> ls, ref float result)
    {
        total = 0;
        if (ls.Count > 0)
        {
            for (int i = 0; i < ls.Count; i++)
            {
                total += ls[i];
            }

            result = (float)(total / (ls.Count * 1f));
            ls.Clear();
        }
    }
}